import java.lang.reflect.*;
import java.sql.*;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

public class MySqlSessionFactory {

    static final String jdbcUrl = "jdbc:mysql://localhost:3306/test";
    static final String dbUser = "root";
    static final String password = "yyqyyds66";

    @SuppressWarnings("all")
    public <T> T getMapper(Class<T> mapperClass){
       return (T)Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{mapperClass},
               new MapperInvocationHandler());
    }

    static class MapperInvocationHandler implements InvocationHandler{
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if(method.getName().startsWith("Select")){
                return invokeSelect(method,proxy,args);
            }
            return null;
        }

        private Object invokeSelect(Method method,Object proxy,Object[] args) {
            String sql = createSql(method);
            System.out.println(sql);
            try (Connection connection = DriverManager.getConnection(jdbcUrl,dbUser,password);
                 PreparedStatement statement = connection.prepareStatement(sql)){
                for(int i=0;i<args.length;i++){
                    Object arg = args[i];
                    if(arg instanceof Integer){
                        statement.setInt(i+1,(int)arg);
                    }else if(arg instanceof String){
                        statement.setString(i+1,arg.toString());
                    }
                }
                ResultSet rs = statement.executeQuery();
                if(rs.next()){
//                    User user = new User();
//                    user.setId(rs.getInt("id"));
//                    user.setAge(rs.getInt("age"));
//                    user.setName(rs.getNString("name"));
//                    return user;
                    return parseResult(rs,method.getReturnType());
                }
            } catch (InvocationTargetException | NoSuchMethodException | InstantiationException |
                     IllegalAccessException | SQLException e) {
                throw new RuntimeException(e);
            }
            return null;
        }

        private Object parseResult(ResultSet rs, Class<?> returnType) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, SQLException {
            Constructor<?> constructor = returnType.getConstructor();
            Object result = constructor.newInstance();
            Field[] declaredFields = returnType.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                Object col = null;
                String name = declaredField.getName();
                if(declaredField.getType() == String.class){
                    col = rs.getString(name);
                }else if(declaredField.getType() == Integer.class){
                    col = rs.getInt(name);
                }
                declaredField.setAccessible(true);
                declaredField.set(result,col);
            }
            return result;
        }

        private String createSql(Method method) {
            StringBuilder sql = new StringBuilder();
            sql.append("select ");
            List<String> selectCols = getSelectCols(method.getReturnType());
            sql.append(String.join(",",selectCols));
            sql.append(" from ");
            String tableName = getSelectTable(method.getReturnType());
            sql.append(tableName);
            sql.append(" where ");
            String where = getSelectWhere(method);
            sql.append(where);
            return sql.toString();
        }

        private String getSelectWhere(Method method) {
            Parameter[] parameters = method.getParameters();
            return Arrays.stream(parameters).map(parameter -> {
                String col = parameter.getAnnotation(Param.class).value();
                return col + " = ?";
            }).collect(Collectors.joining("and"));
        }

        private String getSelectTable(Class<?> returnType) {
            String tableName = returnType.getAnnotation(Table.class).tableName();
            if(tableName == null){
                throw new RuntimeException("returnType can not get table name");
            }
            return tableName;
        }

        private List<String> getSelectCols(Class<?> returnType) {
//            Field[] fields = returnType.getFields();
//            List<String> list = Arrays.stream(fields).map(field -> {
//                String name = field.getName();
//                return name;
//            }).toList();
//            return list;
            Field[] declaredFields = returnType.getDeclaredFields();
            return Arrays.stream(declaredFields).map(Field::getName).toList();
        }


    }
}
